#region Copyright and Licensing Notice
// Copyright (C) 2011 Steven Bosnick
// 
// This program is open source software; you can redistribute it 
// and/or modifiy it under the terms of the Eclipse Public License,
// version 1.0.
// 
// This program is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A 
// PARTICULAR PURPOSE. See the Eclipse Public License for more
// details.
// 
// You should have received a copy of the Eclipse Public License 
// along with this program; if not, you can find a copy at
// "http://www.opensource.org/licenses/eclipse-1.0.php".
#endregion
using System;
using System.Collections.Generic;
using BitwiseOre.SharedMemory.Core;
using BitwiseOre.SharedMemory.Core.Common;
using BitwiseOre.SharedMemory.Core.Primatives;
using Moq;
using NUnit.Framework;

namespace BitwiseOre.Tests.SharedMemory.Primatives
{
    [TestFixture]
    public sealed class GuidQueueFixture : AssertionHelper
    {
        [Test(Description = "Checks that initializeing a new queue makes all indicies available as empty indices")]
        public void InitializeNew_EnqueuesAllIndiciesToEmptyQueue()
        {
            const int count = 5;
            var full = new TestIndexQueue();
            var empty = new TestIndexQueue();
            var queue = new GuidQueue(full, empty, new TestGuidArray(count), null);

            ((ISharedObjectInit)queue).InitializeNew();

            Expect(full.Count, EqualTo(0), 
                "Unexpected indicies in full queue.");
            Expect(empty.Count, EqualTo(count),
                "Insufficient indicies in the empty queue.");
        }

        [Test(Description = "Checks that enqueing an entry moves an index from the empty index queue to the full one")]
        public void Enqueue_MovesIndexFromEmptyToFull()
        {
            var full = new TestIndexQueue();
            var empty = new TestIndexQueue();
            var queue = new GuidQueue(full, empty, new TestGuidArray(5), null);
            ((ISharedObjectInit)queue).InitializeNew();
            var oldemptycount = empty.Count;
            var oldfullcount = full.Count;

            queue.Enqueue(Guid.NewGuid());

            Expect(empty.Count, EqualTo(oldemptycount - 1),
                "Index not removed from empty queue.");
            Expect(full.Count, EqualTo(oldfullcount + 1),
                "Index not added to full queue.");
        }

        [Test(Description = "Checks that enqueing an entry writes the entry to the newly full index in the array")]
        public void Enqueue_WritesEntryToArray()
        {
            var guid = Guid.NewGuid();
            var full = new TestIndexQueue();
            var array = new TestGuidArray(5);
            var queue = new GuidQueue(full, new TestIndexQueue(), array, null);
            ((ISharedObjectInit)queue).InitializeNew();

            queue.Enqueue(guid);
            
            Expect(array[full.Dequeue()], EqualTo(guid),
                "Entry not written to expected index in the array.");
        }

        [Test(Description = "Ensures that attempting to enqueue to a full queue throws an exception")]
        public void Enqueue_ToFullQueue()
        {
            var empty = new Mock<IIndexQueue>();
            empty.Setup(e => e.Dequeue()).Returns(QueueConstants.NullIndex);
            var queue = new GuidQueue(new Mock<IIndexQueue>().Object, empty.Object,
                                      new Mock<IArray<Guid>>().Object, null);

            Expect(() => queue.Enqueue(Guid.NewGuid()), Throws.InvalidOperationException);
        }

        [Test(Description = "Checks that dequing an entry moves an index from the full index queue to the empty one")]
        public void Dequeue_MovesIndexFromFullToEmpty()
        {
            var full = new TestIndexQueue();
            var empty = new TestIndexQueue();
            var queue = new GuidQueue(full, empty, new TestGuidArray(5), null);
            ((ISharedObjectInit)queue).InitializeNew();
            queue.Enqueue(Guid.NewGuid());
            var oldemptycount = empty.Count;
            var oldfullcount = full.Count;

            queue.Dequeue();

            Expect(full.Count, EqualTo(oldfullcount - 1),
                "Index not removed from full queue.");
            Expect(empty.Count, EqualTo(oldemptycount + 1),
                "Index not added to empty queue.");
        }

        [Test(Description = "Checks that dequeing returns a previously enqueued value")]
        public void Dequeue_ReturnsEnqueuedValue()
        {
            var value = Guid.NewGuid();
            var queue = new GuidQueue(new TestIndexQueue(), new TestIndexQueue(), new TestGuidArray(5), null);
            ((ISharedObjectInit)queue).InitializeNew();
            queue.Enqueue(value);

            var result = queue.Dequeue();

            Expect(result, EqualTo(value),
                "Unexpected value dequeued from the queue.");
        }

        [Test(Description = "Ensures that attempting to dequeue from an empty queue throws an exception")]
        public void Dequeue_FromEmptyQueue()
        {
            var full = new Mock<IIndexQueue>();
            full.Setup(f => f.Dequeue()).Returns(QueueConstants.NullIndex);
            var queue = new GuidQueue(full.Object, new Mock<IIndexQueue>().Object,
                                      new Mock<IArray<Guid>>().Object, null);

            Expect(() => queue.Dequeue(), Throws.InvalidOperationException);
        }

        [Test(Description = "Checks that incrementing the pending dequeue count increments the underlying " +
                            "value using an interlocked operation.")]
        public void IncrementPending_DelegatesToInterlockedOperation()
        {
            const int index = 0;
            const int oldvalue = 0;
            var pendingarray = new Mock<IIntArray>();
            pendingarray.Setup(a => a[It.Is<int>(i => i == index)]).Returns(oldvalue);
            pendingarray.Setup(a => a.InterlockedCompareExchange(It.IsAny<int>(), It.IsAny<int>(), It.IsAny<int>()))
                .Returns(true);

            var queue = new GuidQueue(null, null, null, pendingarray.Object);
            queue.IncrementPending();

            pendingarray.Verify(a => 
                a.InterlockedCompareExchange(It.Is<int>(i => i == index),
                It.Is<int>(i => i == oldvalue),
                It.Is<int>(i => i == oldvalue + 1)));
        }

        [Test(Description = "Checks that attempting to decrement with 0 pending dequeue count returns false")]
        public void DecrementNonZeroPending_WithZeroPending()
        {
            const int index = 0;
            const int oldvalue = 0;
            var pendingarray = new Mock<IIntArray>();
            pendingarray.Setup(a => a[It.Is<int>(i => i == index)]).Returns(oldvalue);
            
            var queue = new GuidQueue(null, null, null, pendingarray.Object);
            var result = queue.DecrementNonZeroPending();

            Expect(!result, "Unexpected return value from DecrementNonZeroPending().");
        }

        [Test(Description = "Checkes that decrementing with 0 penidng dequeue count doesn't decrement the " +
                            "underlying value.")]
        public void DecrementNonZeroPending_WithZeroPendingDoesNotDelegate()
        {
            const int index = 0;
            const int oldvalue = 0;
            var pendingarray = new Mock<IIntArray>();
            pendingarray.Setup(a => a[It.Is<int>(i => i == index)]).Returns(oldvalue);
            
            var queue = new GuidQueue(null, null, null, pendingarray.Object);
            queue.DecrementNonZeroPending();

            pendingarray.Verify(a => a.InterlockedCompareExchange(It.IsAny<int>(), It.IsAny<int>(), It.IsAny<int>()),
                                Times.Never(), 
                                "Unexpected attempt to alter the underlying pending dequeue count.");
        }


        [Test(Description = "Checks that attempting to decrement with > 0 pending dequeue count retruns true")]
        public void DecrementNonZeroPending_WithNonZeroPending()
        {
            const int index = 0;
            const int oldvalue = 1;
            var pendingarray = new Mock<IIntArray>();
            pendingarray.Setup(a => a[It.Is<int>(i => i == index)]).Returns(oldvalue);
            pendingarray.Setup(a => a.InterlockedCompareExchange(It.IsAny<int>(), It.IsAny<int>(),
                                                                 It.IsAny<int>()))
                .Returns(true);
            
            var queue = new GuidQueue(null, null, null, pendingarray.Object);
            var result = queue.DecrementNonZeroPending();

            Expect(result, "Unexpected return value from DecrementNonZeroPending().");
            
        }

        [Test(Description = "Checks that attemepting to decrement with > 0 pending dequeue count " +
                            "decrements the underlying value.")]
        public void DecrementNonZeroPending_WithNonZeroPendingDelegatesToInterlockedOperation()
        {
            const int index = 0;
            const int oldvalue = 1;
            var pendingarray = new Mock<IIntArray>();
            pendingarray.Setup(a => a[It.Is<int>(i => i == index)]).Returns(oldvalue);
            pendingarray.Setup(a => a.InterlockedCompareExchange(It.IsAny<int>(), It.IsAny<int>(),
                                                                 It.IsAny<int>()))
                .Returns(true);
            
            var queue = new GuidQueue(null, null, null, pendingarray.Object);
            queue.DecrementNonZeroPending();

            pendingarray.Verify(
                a =>
                a.InterlockedCompareExchange(It.Is<int>(i => i == index), It.Is<int>(i => i == oldvalue),
                                             It.Is<int>(i => i == oldvalue - 1)),
                "Underlying pending dequeue count was not decremented.");
        }


        private sealed class TestGuidArray : IArray<Guid>
        {
            private readonly Guid[] _array;

            public TestGuidArray(int count)
            {
                _array = new Guid[count];
            }

            public Guid this[int index]
            {
                get { return _array[index]; }
                set { _array[index] = value; }
            }

            public int Capacity
            {
                get { throw new NotImplementedException(); }
            }

            public int Count
            {
                get { return _array.Length; }
            }
        }
        private sealed class TestIndexQueue : IIndexQueue
        {
            private readonly Queue<int> _queue = new Queue<int>();

            public void Enqueue(int item)
            {
                _queue.Enqueue(item);
            }

            public int Dequeue()
            {
                return _queue.Dequeue();
            }

            public int Count
            {
                get { return _queue.Count; }
            }
        }
    }
}